﻿class manager{
	//libaries ////////////////////////////////////////////////
	private var unitLibrary:Array;		
	private var cardLibrary:Array;
	private var spellLibrary:Array;
	private var towerBlockLibrary:Array;
	
	//storage of entities /////////////////////////////////////
	private var towerEntities:Array;
	private var cardEntities:Array;	
	private var unitEntities:Array;
	private var spellEntities:Array;
	private var numTowerBlocks:Number;
	private var numUnits:Number;
	private var numCards:Number;
	private var numSpells:Number;
	
	//players //////////////////////////////////////////////////
	private var clientPlayer:player;		//the human player in the game
	private var computerPlayer:aiEntity;	//the computer player in the game
	private var leftPlayer:player;
	private var rightPlayer:player;
	private var leftPlayerAlliance:String;	//stored separately because we need
	private var rightPlayerAlliance:String; //get this before the game starts
	
	// misc
	private var mainLevel;	//level where we should attach things (eg, _root)
	
	public function manager(){
		setupInitialVariables();
	}
	
	/**
	 * Sets up a new game	 */
	public function setupNewGame():Void{
		setupInitialVariables();
		buildUnitLibrary();
		buildCardLibrary();
		setupPlayers();
		setupTowers();
		setupWizards();
		setupCardFrames()
		loadDecks()
		dealHands()
		drawCardIcons();
		//hideCards();
	}
	
	//updates the manager every frame
	public function update(){
		checkCardMouseover(mainLevel._xmouse, mainLevel._ymouse);
		updateCooldownTimers();
		updateAI();
		//checkVictory();
	
		/*//TODO debug
		if(Key.getAscii() == 49){
			hideCards();
			clientPlayer = "left";
		}
		if(Key.getAscii() == 50){
			hideCards();
			clientPlayer = "right";
		}*/
	}
	
	/**
	 * Deals a starting hand to each player	 */
	private function dealHands(){
		leftPlayer.drawStartingHand();
		rightPlayer.drawStartingHand();
	}
	
	/**
	 * Load each player's deck with cards	 */
	private function loadDecks():Void{
		//TODO for now, deck contents are hard-coded here
		//TODO also need to shuffle deck
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		leftPlayer.addCardToDeck(getCardClone("Shadowbolt"));
		leftPlayer.addCardToDeck(getCardClone("Shadowstorm"));
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		leftPlayer.addCardToDeck(getCardClone("Temple of the Damned"));
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		leftPlayer.addCardToDeck(getCardClone("Temple of the Damned"));
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		leftPlayer.addCardToDeck(getCardClone("Summon Trickster"));
		rightPlayer.addCardToDeck(getCardClone("Summon Templar"));
		rightPlayer.addCardToDeck(getCardClone("Summon Templar"));
		rightPlayer.addCardToDeck(getCardClone("Summon Angel"));
		rightPlayer.addCardToDeck(getCardClone("Searing Light"));
		rightPlayer.addCardToDeck(getCardClone("Temple of the Righteous"));
		rightPlayer.addCardToDeck(getCardClone("Healing Touch"));
		rightPlayer.addCardToDeck(getCardClone("Summon Templar"));
	}
	
	/**
	 * Sets up all of the initial variables for a new game	 */
	private function setupInitialVariables():Void{
		unitLibrary = new Array();
		cardLibrary = new Array();
		spellLibrary = new Array();
		towerBlockLibrary = new Array();
		spellEntities = new Array();
		towerEntities = new Array();
		cardEntities = new Array();
		unitEntities = new Array();
		leftPlayer = new player();
		rightPlayer = new player();
		
		numTowerBlocks = 0;
		numUnits = 0;
		numCards = 0;
		numSpells = 0;
		
		//TODO
		clientPlayer = leftPlayer;
		computerPlayer = new aiEntity();
		leftPlayerAlliance = "dark";
		rightPlayerAlliance = "light";
	}
	
	/**
	 * Sets up the players at the beginning of a game
	 * Sets their side and their alliance (dark, light, etc... )	 */
	private function setupPlayers():Void{
		leftPlayer = new player();
		leftPlayer.setSide("left");
		leftPlayer.setAllianceName(leftPlayerAlliance);
		
		rightPlayer = new player();
		rightPlayer.setSide("right");
		rightPlayer.setAllianceName(rightPlayerAlliance);

		clientPlayer = leftPlayer;
		
		//TODO
		computerPlayer.setParent(rightPlayer);
		computerPlayer.setManager(this);
	}
	
	/**
	 * Sets up the wizards for the beginning of a game	 */
	private function setupWizards():Void{
		var leftWizard:unit = mainLevel.attachMovie(leftPlayerAlliance + "mage", "unit" + numUnits, config.unitDepth + numUnits);
		var leftTower:tower = leftPlayer.getTower();
		leftWizard._x = leftTower.getTop()._x + leftTower.getTop()._width / 2;	//center wizard
		leftWizard._y = leftTower.getTop()._y; - leftWizard._height; 
		leftWizard.setOwner("left");
		leftWizard.setParent(leftPlayer);
		numUnits++;
		unitEntities.push(leftWizard);
		leftPlayer.setWizard(leftWizard);
		
		var rightWizard:unit = mainLevel.attachMovie(rightPlayerAlliance + "mage", "unit" + numUnits, config.unitDepth + numUnits);
		var rightTower:tower = rightPlayer.getTower();
		rightWizard._x = rightTower.getTop()._x + rightTower.getTop()._width / 2;	//center wizard
		rightWizard._y = rightTower.getTop()._y; - rightWizard._height; 
		rightWizard.setOwner("right");
		rightWizard.setParent(rightPlayer);
		numUnits++;
		unitEntities.push(rightWizard);
		rightPlayer.setWizard(rightWizard);

		//make units face the right way
		//TODO
		reacquireTargets();
	}
	
	/**
	 * Returns an array containing all of the units in the game	 */
	public function getUnits():Array{
		return unitEntities;	
	}
	
	/**
	 * Draws the card icons for the player's hand at the beginning of a game	 */
	private function drawCardIcons():Void{
		var leftHand:Array = leftPlayer.getHand();
		var rightHand:Array = rightPlayer.getHand();
		
		//loop through each player's hand and draw the card icons for those cards
		for(var i = 0; i < leftHand.length; i++){
			var tmp:MovieClip = mainLevel.attachMovie(leftHand[i].getCardIcon(), "card" + cardEntities.length, config.cardDepth + cardEntities.length);	
			tmp._x = config.leftCardX + i * (tmp._width + config.cardSpacing);
			tmp._y = config.leftCardY;
			cardEntities.push(tmp);
		}
		
		//now do the same thing for the right player's cards
			for(var i = 0; i < rightHand.length; i++){
			var tmp:MovieClip = mainLevel.attachMovie(rightHand[i].getCardIcon(), "card" + cardEntities.length, config.cardDepth + cardEntities.length);	
			tmp._x = config.rightCardX - (i + 1) * (tmp._width + config.cardSpacing);
			tmp._y = config.rightCardY;
			cardEntities.push(tmp);
		}
	}
	
	/**
	 * Sets up the towers at the beginning of a game
	 * Creates and positions them	 */
	private function setupTowers():Void{
		var leftTower:tower  = new tower(config.maxTowerSize, "left");
		var rightTower:tower = new tower(config.maxTowerSize, "right");
		
		var leftBase:MovieClip  = mainLevel.attachMovie("towerBase", "towerPiece" + numTowerBlocks, config.towerDepth + numTowerBlocks++);
		var rightBase:MovieClip = mainLevel.attachMovie("towerBase", "towerPiece" + numTowerBlocks, config.towerDepth + numTowerBlocks++);
		var leftTop:MovieClip  = mainLevel.attachMovie("towerTop",  "towerPiece" + numTowerBlocks, config.towerDepth + numTowerBlocks++);
		var rightTop:MovieClip = mainLevel.attachMovie("towerTop",  "towerPiece" + numTowerBlocks, config.towerDepth + numTowerBlocks++);
		
		//position the bases of the towers
		leftBase._x = config.leftTowerX;
		leftBase._y = config.leftTowerY;
		rightBase._x = config.rightTowerX;
		rightBase._y = config.rightTowerY;
		
		//position the tops of the towers to be just above the bases
		leftTop._x = config.leftTowerX - 10;
		leftTop._y = config.leftTowerY - leftTop._height;
		rightTop._x = config.rightTowerX - 10;
		rightTop._y = config.rightTowerY - rightTop._height;
		
		//add all of these pieces to the towers
		leftTower.addPiece(leftBase);
		leftTower.addPiece(leftTop);
		rightTower.addPiece(rightBase);
		rightTower.addPiece(rightTop);
		
		leftPlayer.setTower(leftTower);
		rightPlayer.setTower(rightTower);
	}
	
	/**
	 * Creates and positions the card frames at the beginning of a game	 */
	private function setupCardFrames():Void{
		//first do all the frames for the left side
		var frameCount:Number = 0;
		for(var i = 0; i < config.handSize; i++){
			var frame:MovieClip = mainLevel.attachMovie("cardFrame", "cardFrame" + frameCount, config.frameDepth + frameCount++);
			var cardWidth = frame._width;
			frame._x = config.leftCardX + i * (cardWidth + config.cardSpacing);
			frame._y = config.leftCardY;
		}
		
		//then do the right side frames
		for(var i = 0; i < config.handSize; i++){
			var frame:MovieClip = mainLevel.attachMovie("cardFrame", "cardFrame" + frameCount, config.frameDepth + frameCount++);
			var cardWidth = frame._width;
			frame._x = config.rightCardX - (i+1) * (cardWidth + config.cardSpacing);
			frame._y = config.rightCardY;
		}
	}
	
	/**
	 * Sets the mainLevel for this manager 
	 * @param m the level to set too (eg, _root)	 **/
	public function setMainLevel(m):Void{
		mainLevel = m;
	}
	
	/**
	 * Adds a card to the library
	 * @param c the card to be added	 */
	public function addCard(c:card):Void{
		cardLibrary[c.getName()] = c.clone();
	}
	
	/**
	 * Returns a copy of the card from the library with cardName
	 * @param cardName the name of the card to return
	 * @return a copy of the specified card	 */
	public function getCardClone(cardName:String):card{
		return(cardLibrary[cardName].clone());
	}
	
	/**
	 * Adds the spell to the library
	 * @param s The spell to be added	 */
	public function addSpell(s:spell):Void{
		spellLibrary[s.getName()] = s.clone();
	}
	
	/**
	 * Adds a unit to the library
	 * @param u The unit to be added	 */
	public function addUnit(u:unit):Void{
		unitLibrary[u.getName()] = u.clone();
	}
	
	public function addTowerBlock(t:towerBlock):Void{
		towerBlockLibrary[t.getName()] = t.clone();
	}
	
	public function getTowerBlockClone(towerBlockName:String):towerBlock{
		return (towerBlockLibrary[towerBlockName].clone());	
	}
	
	/**
	 * Returns a copy of the unit from the library with unitName
	 * @param unitName The name of the unit to return
	 * @return a copy of the unit specified	 */
	public function getUnitClone(unitName:String):unit{
		return(unitLibrary[unitName].clone());
	}
	
	/**
	 * Returns a copy of the unit from the library with spellName
	 * @param spellNme The name of the spell to return
	 * @return a copy of the spell specified	 */
	public function getSpellClone(spellName:String):spell{
		return(spellLibrary[spellName].clone());
	}
	
	/**
	 * Creates an instance of every tower piece that can be copied later	 */
	private function buildTowerBlockLibrary():Void{
		var t:towerBlock = new towerBlock();
		t.initTowerBlock("Temple of the Damned", 30);
		addTowerBlock(t.clone());
		t.initTowerBlock("Temple of the Righteous", 30);
		addTowerBlock(t.clone());
	}
	
	/**
	 * Creates an instance of every spell that can be copied later	 */
	private function buildSpellLibrary():Void{
		var s:spell = new spell();
		s.initSpell("Shadowstorm", "omni_cast", 8, 10, 2);
		addSpell(s.clone());
	}
	
	/**
	 * Creates an instance of every unit that can be copied later	 */
	private function buildUnitLibrary():Void{
		var u:unit = new unit();
		u.initUnit("Summon Trickster", 20, 3, 7, 1, 500, 5);
		addUnit(u.clone());
		u.initUnit("Summon Angel", 30, 2, 15, 2, 500, 5);
		addUnit(u.clone());
		u.initUnit("Summon Templar", 50, 1, 15, 7, 1200, 5);
		addUnit(u.clone());
	}
	
	/**
	 * Creates an instance of every card that can be copied later	 */
	private function buildCardLibrary():Void{
		var c:card = new card();
		c.initCard("Shadowbolt", "Engulfs enemy in shadow, dealing 10-15 damage", "icoShadowbolt", 100, 1, "projectileSpell", "spShadowbolt");
		addCard(c.clone());
		c.initCard("Summon Trickster", "Summons an trickster from the Nether", "icoSummonTrickster", 200, 1, "lightunit", "uTrickster");
		addCard(c.clone());
		c.initCard("Temple of the Damned", "Enemy units deal -1 damage while this structure exists", "icoTempleDamned", 300, 3, "buffbuilding", "bTempleDamned");
		addCard(c.clone());
		c.initCard("Shadowstorm", "Engulfs an area, dealing 5-7 damage to units in the area", "icoShadowstorm", 101, 2, "aoespell", "spShadowstorm");
		addCard(c.clone());
		c.initCard("Healing Touch", "Heals selected unit for 20", "icoHealingTouch", 115, 1, "omniheal", "spHealingTouch");
		addCard(c.clone());
		c.initCard("Searing Light", "Pierces enemy's soul, dealing 15 damage", "icoSearingLight", 116, 1, "omnispell", "spSearingLight");
		addCard(c.clone());
		c.initCard("Summon Angel", "Summons an angel from the Heavens", "icoSummonAngel", 208, 2, "mediumunit", "uAngel");
		addCard(c.clone());
		c.initCard("Temple of the Righteous", "Friendly units that are killed have a 10% change of being resurrected", "icoTempleRighteous", 310, 3, "buffbuilding", "bTempleRighteous")
		addCard(c.clone());
		c.initCard("Summon Templar", "Holy units deal an additional +1 damage and all Dark environment effects are dispelled", "icoSanctity", 117, 3, "heavyunit", "uTemplar")
		addCard(c.clone());
	}	
	
	/**
	 * Given a card, returns the card text to be displayed
	 * @param c the card in question
	 * @return the card text to be displayed	 */
	private function generateCardDescriptionText(c:card):String{
		return c.getName() + "\n" + c.getDescription();
	}
	
	/**
	 * Updates the cooldown timers that show on the gamefield	 */
	private function updateCooldownTimers():Void{
		 if(leftPlayer.getCooldown() > 0)
		 	mainLevel["leftCooldown"].text = leftPlayer.getCooldown();
		 else
		 	mainLevel["leftCooldown"].text = "";
		 	
		 if(leftPlayer.getCooldown() > 0)
		 	mainLevel["rightCooldown"].text = rightPlayer.getCooldown();
		 else
		 	mainLevel["rightCooldown"].text = "";
	}
	
	/**
	 * Updates the AI and makes decisions based on current conditions	 */
	public function updateAI():Void{
		computerPlayer.update();
	}
	
	/**
	 * Called when a player uses one of their cards
	 * @param p the player using the card
	 * @param c the card used by the player	 */
	public function useCard(p:player, c:card):Void{
		var hand:Array = p.getHand();
		cardProcessor(c, p); //process the selected card
		utility.arrayRemove(hand, c);	//remove card from play and draw a new one
		p.drawCard();
		
		//remove all icons and redraw them (kind of lazy)
		removeAllCardIcons();
		drawCardIcons();		
	}
	
	/**
	 * Called by the main frame to notify the manager of a click
	 * @param x the x-coordinate of the mouse click
	 * @param y the y-coordinate of the mouse click	 */
	public function handleMouseClick(x:Number, y:Number):Void{
		var cardNum:Number = checkCardMouseover(x, y);
		// if c is non-negative, we must have clicked a card
		//make sure we're not currently under cooldown
		//TODO make sure that we own the card
		if(cardNum != -1 && clientPlayer.canCast()){
			//remove the card from the player 
			//only the actual human player would 'click' a card
			var hand:Array = clientPlayer.getHand();
			
			//if cardNum is > config.handSize, the click is on the right-side cards
			if(cardNum >= config.handSize)
				cardNum -= config.handSize;
			
			var clickedCard:card = hand[cardNum];
			//process the clicked card

			useCard(clientPlayer, clickedCard);

			/*cardProcessor(clickedCard, clientPlayer);
			
			//remove card from play and draw a new card
			utility.arrayRemove(hand, clickedCard);
			var newCard:card = clientPlayer.drawCard();
			
			//remove all of the icons and redraw them (kind of lazy)
			removeAllCardIcons();
			drawCardIcons();*/
		}
	}
	
	/**
	 * Notifies the manager of a key press; act accordingly
	 * @param key the key that has been pressed	 */
	public function buttonPress(key:Number):Void{
		switch(key){
			case 49:{ //1
				clientPlayer = leftPlayer;
			} break;
			
			case 50:{ //2
				clientPlayer = rightPlayer;
			} break;	
		}		
	}
	
	/**
	 * Removes all card icons from play	 */
	private function removeAllCardIcons(){
		for(var i = 0; i < cardEntities.length; i++){
			mainLevel["card" + i].removeMovieClip();	
		}	
		cardEntities = new Array();
		numCards = 0;
	}
	
	/**
	 * Checks to see if the player is currently mousing over one of his cards
	 * If so, show its information in the middle of the screen
	 * TODO Make sure this is only possible for a card they own (eg, not mousing enemy cards)
	 * @param x the x-coordinate of the mouse cursor
	 * @param y the y-coordinate of the mouse cursor
	 * @return the number of the card the player is mousing over; -1 if none	 */
	public function checkCardMouseover(x:Number, y:Number):Number{
		for(var i = 0; i < cardEntities.length; i++){
			if(cardEntities[i].hitTest(x, y, true)){
				//figure out which card in the player's hand the frame corresponds to
				if(i < config.handSize){
					//if i is to the left, player is mousing over the left cards
					var leftHand:Array = leftPlayer.getHand();
					mainLevel["cardText"].text = generateCardDescriptionText(leftHand[i]);
					return i;
				}
				else{
					//we must be hovering over a card on the right
					var rightHand:Array = rightPlayer.getHand();
					mainLevel["cardText"].text = generateCardDescriptionText(rightHand[i - config.handSize]);
					return i;
				}	
				
			}
		}	
		
		//no mouse overs
		mainLevel["cardText"].text = "";
		return -1;
	}
	
	/**
	 * Processes the card, c, as used by player, p
	 * @param c the card that has been played
	 * @param p the player that has played the card	 */
	private function cardProcessor(c:card, p:player):Void{
		switch(c.spellID){
			
			case 101:{ //101 Shadowstorm
				createSpell(c, p);
			} break;
			
			case 116: { //116 Searing Light
				createSpell(c, p);
			} break;
			
			case 117:{ //117 Summon Templar (should be Sanctity)
				createUnit(c, p);
			} break;
			
			case 200:{ //200 - Summon Imp
				createUnit(c, p);//TODO
			} break;
			
			case 208:{ //208 - Summon Angel
				createUnit(c, p);//TODO
			} break;
			
			case 300:{ //300 - Summon Temple of the Damned
				createTowerBlock(c, p);
			} break;
			
			case 310:{ //300 - Summon Temple of the Righteous
				createTowerBlock(c, p);
			} break;
		}
		
		//all units should reacquire targets
		reacquireTargets();
	}
	
	/**
	 * Creates the towerBlock associated with card c for player p
	 * @param c the card that has been played
	 * @param p the player that has played the card	 */
	private function createTowerBlock(c:card, p:player):Void{
		//start the cooldown for the effected player
		p.startCooldown(c.cooldown);
		var tmpTowerBlock:towerBlock = getTowerBlockClone(c.getName());	
		
		//attach the new towerBlock to the scene
		var newTowerBlock = mainLevel.attachMovie(c.graphic, "tower" + numTowerBlocks, config.towerDepth + numTowerBlocks++);
		tmpTowerBlock.duplicate(newTowerBlock);

		p.getTower().addPieceAfterTop(newTowerBlock, p.getWizard());
		
		towerEntities.push(newTowerBlock);
	}
	
	/**
	 * Creates the spell associated with card c for player p
	 * @param c the card that has been played
	 * @param p the player that has played the card	 */
	private function createSpell(c:card, p:player):Void{
		//start the cooldown for the effected player
		p.startCooldown(c.cooldown);
		var tmpSpell:spell = getSpellClone(c.getName());
		
		//attach the new spell to the scene and duplicate the stats of tmpSpell
		var newSpell = mainLevel.attachMovie(c.graphic, "spell" + numSpells, config.spellDepth + numSpells++);
		tmpSpell.duplicate(newSpell);
		
		//define the newly created spell
		newSpell._x = mainLevel._xmouse;
		newSpell._y = mainLevel._ymouse;
		
		spellEntities.push(newSpell);
	}
	
	/**
	 * Creates the unit associated with card c for player p
	 * @param c the card that has been played
	 * @param p the player that has played the card	 */
	private function createUnit(c:card, p:player):Void{
		//start the cooldown for the effected player
		p.startCooldown(c.cooldown);
		var tmpUnit:unit = getUnitClone(c.getName());

		//attach the new unit to the scene and duplicate the stats of tmpUnit
		var newUnit = mainLevel.attachMovie(c.graphic, "unit" + numUnits, config.unitDepth + numUnits++);
		tmpUnit.duplicate(newUnit);

		//define the newly created unit
		newUnit.setOwner(p.getSide());
		newUnit.setParent(p);
		newUnit.setManager(this);
		newUnit._x = getUnitSpawnPointX(p.getSide());
		newUnit._y = getUnitSpawnPointY(p.getSide());
		newUnit.setAIControl(true);
		
		unitEntities.push(newUnit);
		
		//TODO
		//reacquire targets	
	}
	
	/**
	 * Retrieve the tower of the player opposite p	 */
	public function getEnemyTower(p:player):tower{
		if(p == leftPlayer)
			return rightPlayer.getTower();
		else if(p == rightPlayer)
			return leftPlayer.getTower();
		else
			trace("Error in getEnemyTower");
	}
	
	/**
	 * Loops through all the units and has them reacquire their target	 */
	private function reacquireTargets():Void{
		for(var i = 0; i < unitEntities.length; i++){
			unitEntities[i].acquireTarget();
		}
	}
	
	/**
	 * Returns the x-coordinate of where the unit should spawn, based on the side
	 * @param side the side on which the unit is spawning	 */
	private function getUnitSpawnPointX(side:String):Number{
		if(side == "left")
			return config.leftTowerX;
		else if (side == "right")
			return config.rightTowerX;	
	}
	
	/**
	 * Returns the y-coordinate of where the unti should spawn, based on the side
	 * @param side the side on which the unit is spawning	 */
	private function getUnitSpawnPointY(side:String):Number{
		if(side == "left")
			return config.leftTowerY;
		else if (side == "right")
			return config.rightTowerY;
	}
	
	/**
	 * Callback function called by a unit when it dies
	 * @param u the the unit that called the event	 */
	public function unitDeathEvent(u:unit):Void{
		utility.arrayRemove(unitEntities, u);
	}
}